class_name Player

extends CharacterBody2D

# 方向
enum Direction {
	LEFT = -1,
	RIGHT = +1,
}

# 状态集
enum State {
	# 站立
	IDLE,
	# 奔跑
	RUNNING,
	# 跳跃
	JUMP,
	# 下落
	FALL,
	# 落地
	LANDING,
	# 滑墙
	WALL_SLIDING,
	# 蹬墙跳
	WALL_JUMP,
	# 攻击
	ATTACK_1,
	ATTACK_2,
	ATTACK_3,
	HURT,
	DYING,
	SLIDING_START,
	SLIDING_LOOP,
	SLIDING_END,
}

# 地面状态
const GROUND_STATES := [
	State.IDLE, State.RUNNING, State.LANDING,
	State.ATTACK_1, State.ATTACK_2, State.ATTACK_3,
]
	
# 奔跑速度
const RUN_SPEED := 160.0
# 地面加速度
const FLOOR_ACCELERATION := RUN_SPEED / 0.2
# 空中加速度
const AIR_ACCELERATION := RUN_SPEED / 0.1
# 跳跃速度（y < 0 朝上）
const JUMP_VELOCITY := -365.0
# 墙面跳跃速度（x 横向速度， y 纵向速度）
const WALL_JUMP_VELOCITY := Vector2(380, -280)
# 滑铲时间
const SLIDING_DURATION := 0.3
# 滑铲速度
const SLIDING_SPEED := 200

# 是否可以连击
@export var can_combo := false
# 受击后退距离
@export var KNOCKBACK_AMOUNT := 512.0
# 默认朝向
@export var direction := Direction.RIGHT:
	set(v):
		direction = v
		if not is_node_ready():
			await ready
		# 更新朝向
		graphics.scale.x = direction
# 重力加速度
var default_gravity := ProjectSettings.get("physics/2d/default_gravity") as float
# 是否为第一帧
var is_first_tick = false
# 是否按下攻击键
var is_combe_requested = false
var pending_damage: Damage
# 可交互对象
var interacting_with_list: Array[Interactable]

@onready var graphics: Node2D = $Graphics
@onready var animation_player: AnimationPlayer = $AnimationPlayer
@onready var coyote_timer: Timer = $CoyoteTimer
@onready var jump_request_timer: Timer = $JumpRequestTimer
@onready var hand_checker: RayCast2D = $Graphics/HandChecker
@onready var foot_checker: RayCast2D = $Graphics/FootChecker
@onready var state_machine: StateMachine = $StateMachine
@onready var stats: Stats = Game.player_stats
@onready var invincible_timer: Timer = $InvincibleTimer
@onready var interaction_icon: AnimatedSprite2D = $InteractionIcon

func _ready() -> void:
	# 解决转场下蹲问题
	stand(default_gravity, 0.01)

# 监听输入事件
func _unhandled_input(event: InputEvent) -> void:
	if event.is_action_pressed("jump"):
		jump_request_timer.start()
	if event.is_action_released("jump"):
		jump_request_timer.stop()
		# 实现提前落地
		if velocity.y < JUMP_VELOCITY / 2:
			velocity.y = JUMP_VELOCITY / 2
	if event.is_action_pressed("attack") and can_combo:
		is_combe_requested = true
	if event.is_action_pressed("interact") and not interacting_with_list.is_empty():
		# 触发最后一个对象
		interacting_with_list.back().interact()

# 实现状态机方法
func tick_physics(state: State, delta: float) -> void:
	# 出现可交互对象，展示交互按钮
	interaction_icon.visible = not interacting_with_list.is_empty()
	
	if invincible_timer.time_left > 0:
		# 无敌时间玩家闪烁（0 ～ 1）
		graphics.modulate.a = sin(Time.get_ticks_msec() / 25.0) * 0.5 + 0.5
	else:
		graphics.modulate.a = 1
	
	match state:
		State.IDLE:
			move(default_gravity, delta)
		State.RUNNING:
			move(default_gravity, delta)
		State.JUMP:
			move(0.0 if is_first_tick else default_gravity, delta)
		State.FALL:
			move(default_gravity, delta)
		State.LANDING:
			stand(default_gravity, delta)
		State.WALL_SLIDING:
			# 墙面滑行，重力减少
			move(default_gravity / 3, delta)
			# 面向墙面滑墙
			direction = Direction.RIGHT if get_wall_normal().x < 0 else Direction.LEFT
		State.WALL_JUMP:
			if state_machine.state_time < 0.1:
				stand(0.0 if is_first_tick else default_gravity, delta)
				# 背朝墙面跳跃
				direction = Direction.LEFT if get_wall_normal().x < 0 else Direction.RIGHT
			else:
				move(default_gravity, delta)
		State.ATTACK_1, State.ATTACK_2, State.ATTACK_3:
			stand(default_gravity, delta)
		State.HURT, State.DYING:
			stand(default_gravity, delta)
		State.SLIDING_END:
			stand(default_gravity, delta)
		State.SLIDING_LOOP, State.SLIDING_START:
			slide(delta)
	is_first_tick = false

func register_interactable(v: Interactable) -> void:
	if state_machine.current_state == State.DYING:
		return
	if v in interacting_with_list:
		return
	interacting_with_list.append(v)

func un_register_interactable(v: Interactable) -> void:
	interacting_with_list.erase(v)

func move(gravity: float, delta: float) -> void:
	var movement := Input.get_axis("move_left", "move_right")
	var acceleration = FLOOR_ACCELERATION if is_on_floor() else AIR_ACCELERATION
	velocity.x = move_toward(velocity.x, movement * RUN_SPEED, acceleration * delta)
	velocity.y += gravity * delta
	# 水平翻转角色
	if not is_zero_approx(movement):
		direction = Direction.LEFT if movement < 0 else Direction.RIGHT
	move_and_slide()

func stand(gravity: float, delta: float) -> void:
	var acceleration = FLOOR_ACCELERATION if is_on_floor() else AIR_ACCELERATION
	velocity.x = move_toward(velocity.x, 0.0, acceleration * delta)
	velocity.y += gravity * delta
	move_and_slide()

func slide(delta: float) -> void:
	velocity.x = direction * SLIDING_SPEED
	velocity.y += default_gravity * delta
	move_and_slide()
	
func die() -> void:
	get_tree().reload_current_scene()

func can_wall_slide() -> bool:
	return hand_checker.is_colliding() and foot_checker.is_colliding()

func get_next_state(current_state: State) -> int:
	if stats.health <= 0:
		return StateMachine.KEEP_CURRENT if current_state == State.DYING else State.DYING
	if pending_damage:
		return State.HURT

	var can_jump = is_on_floor() or coyote_timer.time_left > 0
	var should_jump = can_jump and jump_request_timer.time_left > 0
	if should_jump:
		return State.JUMP
	if current_state in GROUND_STATES and not is_on_floor():
		return State.FALL
	
	var movement := Input.get_axis("move_left", "move_right")
	var is_still = is_zero_approx(movement) and is_zero_approx(velocity.x)
	
	match current_state:
		State.IDLE:
			if not is_still:
				return State.RUNNING
			if Input.is_action_pressed("attack"):
				return State.ATTACK_1
			if Input.is_action_pressed("slide"):
				return State.SLIDING_START
			
		State.RUNNING:
			if is_still:
				return State.IDLE
			if Input.is_action_pressed("slide"):
				return State.SLIDING_START
			if Input.is_action_pressed("attack"):
				return State.ATTACK_1
		State.JUMP:
			# y 速度向量为 0
			if velocity.y >= 0:
				return State.FALL
		State.FALL:
			if is_on_floor():
				return State.LANDING if is_still else State.RUNNING
			if can_wall_slide():
				return State.WALL_SLIDING
				
		State.LANDING:
			if not is_still:
				return State.RUNNING
			if not animation_player.is_playing():
				return State.IDLE
				
		State.WALL_SLIDING:
			if jump_request_timer.time_left > 0:
				return State.WALL_JUMP
			if is_on_floor():
				return State.IDLE
			if not can_wall_slide():
				return State.FALL
		
		State.WALL_JUMP:
			if can_wall_slide() and not is_first_tick:
				return State.WALL_SLIDING
			if velocity.y >= 0:
				return State.FALL

		State.ATTACK_1:
			if not animation_player.is_playing():
				return State.ATTACK_2 if is_combe_requested else State.IDLE
		State.ATTACK_2:
			if not animation_player.is_playing():
				return State.ATTACK_3 if is_combe_requested else State.IDLE
		State.ATTACK_3:
			if not animation_player.is_playing():
				return State.IDLE
		State.HURT:
			if not animation_player.is_playing():
				return State.IDLE
				
		State.SLIDING_START:
			if not animation_player.is_playing():
				return State.SLIDING_LOOP
		State.SLIDING_LOOP:
			if state_machine.state_time > SLIDING_DURATION:
				return State.SLIDING_END
		State.SLIDING_END:
			if not animation_player.is_playing():
				return State.IDLE
				
	return StateMachine.KEEP_CURRENT

func transition_state(from: State, to: State) -> void:
	print("[%s] %s => %s" % [
		Engine.get_physics_frames(),
		State.keys()[from] if from != -1 else "<START>",
		State.keys()[to]
	])
	
	# 在离开需要循环音效的状态时停止音效
	match from:
		State.RUNNING:
			SoundManager.stop_looping_sfx("Run")

	if from not in GROUND_STATES and to in GROUND_STATES:
		coyote_timer.stop()
	
	match to:
		State.IDLE:
			animation_player.play("idle")
		State.RUNNING:
			animation_player.play("running")
			SoundManager.play_looping_sfx("Run")
		State.JUMP:
			animation_player.play("jump")
			velocity.y = JUMP_VELOCITY
			coyote_timer.stop()
			jump_request_timer.stop()
			SoundManager.play_sfx("Jump")
		State.FALL:
			animation_player.play("fall")
			if from in GROUND_STATES:
				coyote_timer.start()
		State.LANDING:
			animation_player.play("landing")
			SoundManager.play_sfx("JumpLand")
		State.WALL_SLIDING:
			animation_player.play("wall_sliding")
		State.WALL_JUMP:
			animation_player.play("jump")
			velocity = WALL_JUMP_VELOCITY
			# 调整朝向
			velocity.x *= sign(get_wall_normal().x)
			jump_request_timer.stop()
			SoundManager.play_sfx("Jump")
		State.ATTACK_1:
			animation_player.play("attack_1")
			is_combe_requested = false;
			SoundManager.play_sfx("Attack1")
		State.ATTACK_2:
			animation_player.play("attack_2")
			is_combe_requested = false;
			SoundManager.play_sfx("Attack2")
		State.ATTACK_3:
			animation_player.play("attack_3")
			is_combe_requested = false;
			SoundManager.play_sfx("Attack3")
		State.HURT:
			animation_player.play("hurt")
			# 屏幕震动
			Game.shake_camera(4)
			stats.health -= pending_damage.amount
			# 受击后退
			var dir := pending_damage.source.global_position.direction_to(global_position)
			velocity = dir * KNOCKBACK_AMOUNT
			pending_damage = null
			SoundManager.play_sfx("Hurt")
			
			# 进入无敌时间
			invincible_timer.start()
			
		State.SLIDING_START:
			animation_player.play("sliding_start")
		State.SLIDING_LOOP:
			animation_player.play("sliding_loop")
		State.SLIDING_END:
			animation_player.play("sliding_end")
		State.DYING:
			interacting_with_list.clear()
			animation_player.play("die")
			invincible_timer.stop()
			SoundManager.play_sfx("Die")
	
	# debug: 状态减速
	#if to == State.WALL_JUMP:
		#Engine.time_scale = 0.3
	#if from == State.WALL_JUMP:
		#Engine.time_scale = 1.0
		
	is_first_tick = true

func _on_hur_box_hurt(hitBox: HitBox) -> void:
	if invincible_timer.time_left > 0:
		return
	
	pending_damage = Damage.new()
	pending_damage.amount = 1
	pending_damage.source = hitBox.owner


func _on_hit_box_hit(hurtbox: HurtBox) -> void:
	Game.shake_camera(2)
	# 顿帧
	Engine.time_scale = 0.01
	await get_tree().create_timer(0.05, true, false, true).timeout
	Engine.time_scale = 1
